import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;

public class BankTest {
    private int nAccounts;
    private int maxInitAmount;
    private int nThreads;
    private int maxTransfer;
    private Bank bank;
    private AtomicInteger nTransactions;
    //--------------------------------------------------------
    public BankTest(int nAccounts, int maxInitAmount, int nThreads, int maxTransfer) {
        this.nAccounts = nAccounts;
        this.maxInitAmount = maxInitAmount;
        this.nThreads = nThreads;
        this.maxTransfer = maxTransfer;
        nTransactions = new AtomicInteger(0);
    }
    //--------------------------------------------------------
    public void initBank() {
        Account[] accounts = new Account[nAccounts];
        for (int i = 0; i < nAccounts; ++i) {
            Account account = new Account(i,
                    (int) (Math.random() * maxInitAmount));
            accounts[i] = account;
        }
        bank = new Bank(accounts);
        bank.calcTotalAmount();
    }
    //-------------------------------------------------------
    public Account[] getRandomPair() {
        Account[] fromToAccounts = new Account[2];
        int[] index = new int[2];
        while ((fromToAccounts[0] == null) || (fromToAccounts[1] == null)) {
            try {
                index[0] = (int) (Math.random() * maxInitAmount);
                index[1] = (int) (Math.random() * maxInitAmount);
                if (index[0] != index[1]) {
                    fromToAccounts[0] = bank.getAccount(index[0]);
                    fromToAccounts[1] = bank.getAccount(index[1]);
                }
            } catch (IllegalArgumentException e) {
                fromToAccounts[0] = null;
                fromToAccounts[1] = null;
            }
        }
        return fromToAccounts;
    }
    //------------------------------------------------------
    public void runThreads() {
        ExecutorService es = Executors.newFixedThreadPool(10);
        List<Future<?>> futures = new ArrayList<>(nThreads);
        System.out.println("Amount before: " + bank.getTotalAmount());
        for (int i = 0; i < nThreads; ++i) {
            Runnable task;
            task = new Runnable() {
                private final Account[] transferAccounts = getRandomPair();
                @Override
                public void run() {
                    bank.transfer(transferAccounts[0], transferAccounts[1],
                            (int) (Math.random() * maxTransfer));
                    nTransactions.incrementAndGet();
                }
            };
            futures.add(es.submit(task));
        }
        for (int i = 0; i < nThreads; ++i) {
            try {
                futures.get(i).get();
            } catch (InterruptedException e) {
                System.out.println("Transfer interrupted");
                es.shutdown();
                return;
            } catch (ExecutionException e) {
                System.out.println("Transfer execution interrupted");
                es.shutdown();
                return;
            }
        }
        System.out.println("Amount after: " + bank.calcTotalAmount());
        System.out.println("Number of transactions: " + nTransactions);
        System.out.println("Number of threads " + nThreads);
        es.shutdown();
    }
}
